home *** CD-ROM | disk | FTP | other *** search
/ Collection of Internet / Collection of Internet.iso / infosrvr / dev / scott / WWW / NextStep / Implementation / old / NewsAccess.m < prev    next >
Text File  |  1992-11-24  |  25KB  |  902 lines

  1. //                                NewsAccess.m
  2.  
  3. // A HyperAccess object provides access to hyperinformation, using particular
  4. //    protocols and data format transformations. This one provides access to
  5. //    the Internet/Usenet News system using NNTP/TCP().
  6.  
  7. // History:
  8. //    26 Sep 90    Written TBL
  9.  
  10. #define NEWS_PORT 119        /* See rfc977 */
  11. #define APPEND            /* Use append methods */
  12. #define MAX_CHUNK    40    /* Largest number of articles in one window */
  13. #define CHUNK_SIZE    20    /* Optimum number of articles for quick display */
  14.  
  15. #import "NewsAccess.h"
  16. #import <defaults/defaults.h>
  17. #import "Anchor.h"
  18. #import "HTParse.h"
  19. #import "HTStyle.h"
  20. #import <ctype.h>
  21.  
  22. extern HTStyleSheet * styleSheet;
  23.  
  24. #define NEXT_CHAR next_char()
  25. #define LINE_LENGTH 512            /* Maximum length of line of ARTICLE etc */
  26. #define GROUP_NAME_LENGTH    256    /* Maximum length of group name */
  27. /*    Module parameters:
  28. **    -----------------
  29. **
  30. **  These may be undefined and redefined by syspec.h
  31. */
  32.  
  33. #define NETCLOSE close        /* Routine to close a TCP-IP socket        */
  34. #define NETREAD  read        /* Routine to read from a TCP-IP socket    */
  35. #define NETWRITE write        /* Routine to write to a TCP-IP socket    */
  36.  
  37. #ifdef NeXT
  38. #import <libc.h>        /* NeXT has all this packaged up */
  39. #define ntohs(x) (x)
  40. #define htons(x) (x)
  41. #else
  42. #include <string.h>        /* For bzero etc */
  43. #include <stdio.h>
  44.  
  45. /*    TCP-specific types
  46. */
  47. #include <sys/types.h>
  48. #include <sys/socket.h>
  49. #include <errno.h>        /* independent */
  50. #include <sys/time.h>        /* independent */
  51. extern char *malloc();
  52. extern void free();
  53. extern char *strncpy();
  54. #endif
  55.  
  56. #include <netinet/in.h>
  57. #include <arpa/inet.h>        /* Must be after netinet/in.h */
  58. #include <netdb.h>
  59. #import <streams/streams.h>
  60. #import "HTUtils.h"        /* Coding convention macros */
  61.  
  62. @implementation NewsAccess
  63.  
  64. //    Module-wide variables
  65.  
  66. static const char * NewsHost;
  67. static struct sockaddr_in soc_address;        /* Binary network address */
  68. static int s;                    /* Socket for conn. to NewsHost */
  69. static char response_text[LINE_LENGTH+1];    /* Last response from NewsHost */
  70. static HyperText *    HT;            /* the new hypertext          */
  71. static int    diagnostic;            /* level: 0=none 1=rtf 2=source */
  72.  
  73. static HTStyle *addressStyle;            /* For heading, from address etc */
  74. static HTStyle *textStyle;            /* Text style */
  75.  
  76. #define INPUT_BUFFER_SIZE 4096
  77. static char input_buffer[INPUT_BUFFER_SIZE];        /* Input buffer */
  78. static char * input_read_pointer;
  79. static char * input_write_pointer;
  80.  
  81.  
  82. /*    Procedure: Read a character from the input stream
  83. **    -------------------------------------------------
  84. */
  85. PRIVATE char next_char(void)
  86. {
  87.     int status;
  88.     if (input_read_pointer >= input_write_pointer) {
  89.     status = read(s, input_buffer, INPUT_BUFFER_SIZE);    /*  Get some more data */
  90.     if (status <= 0) return (char)-1;
  91.     input_write_pointer = input_buffer + status;
  92.     input_read_pointer = input_buffer;
  93.     }
  94.     return *input_read_pointer++;
  95. }
  96.  
  97.  
  98. //    Initialisaion for this class
  99. //    ----------------------------
  100. //
  101. //    We pick up the NewsHost name from, in order:
  102. //
  103. //    1.    WorldWideWeb
  104. //    2.    Global
  105. //    3.    News
  106. //    4.    Defualt to cernvax.cern.ch    (!!!)
  107.  
  108. + initialize
  109. {
  110.     const struct hostent  *phost;                /* Pointer to host - See netdb.h */
  111.     struct sockaddr_in* sin = &soc_address;
  112.     
  113. /*  Set up defaults:
  114. */
  115.     sin->sin_family = AF_INET;            /* Family = internet, host order  */
  116.     sin->sin_port = NEWS_PORT;                /* Default: new port,    */
  117.  
  118. /*   Get name of Host
  119. */
  120.     if ((NewsHost = NXGetDefaultValue("WorldWideWeb","NewsHost"))==0)
  121.         if ((NewsHost = NXGetDefaultValue("News","NewsHost")) == 0)
  122.         NewsHost = "cernvax.cern.ch";
  123.  
  124.     if (*NewsHost>='0' && *NewsHost<='9') {   /* Numeric node address: */
  125.     sin->sin_addr.s_addr = inet_addr((char *)NewsHost); /* See arpa/inet.h */
  126.  
  127.     } else {            /* Alphanumeric node name: */
  128.     phost=gethostbyname((char*)NewsHost);    /* See netdb.h */
  129.     if (!phost) {
  130.         NXRunAlertPanel(NULL, "Can't find internet node name `%s'.",
  131.             NULL,NULL,NULL,
  132.         NewsHost);
  133.         CTRACE(tfp,
  134.           "NewsAccess: Can't find internet node name `%s'.\n",NewsHost);
  135.         return nil;  /* Fail */
  136.     }
  137.     memcpy(&sin->sin_addr, phost->h_addr, phost->h_length);
  138.     }
  139.  
  140.     if (TRACE) printf( 
  141.     "NewsAccess: Parsed address as port %4x, inet %d.%d.%d.%d\n",
  142.         (unsigned int)ntohs(sin->sin_port),
  143.         (int)*((unsigned char *)(&sin->sin_addr)+0),
  144.         (int)*((unsigned char *)(&sin->sin_addr)+1),
  145.         (int)*((unsigned char *)(&sin->sin_addr)+2),
  146.         (int)*((unsigned char *)(&sin->sin_addr)+3));
  147.  
  148.     s=-1;        /* Disconnected */
  149.     
  150.     return self;
  151. }
  152.  
  153.  
  154. //    Return the name of the access
  155. //    -----------------------------
  156.  
  157. - (const char *)name
  158. {
  159.     return "news";
  160. }
  161.  
  162.  
  163. //    Get Styles from stylesheet
  164. //
  165. static void get_styles()
  166. {
  167.     if (!addressStyle) addressStyle = HTStyleNamed(styleSheet, "Address");
  168.     if (!textStyle) textStyle = HTStyleNamed(styleSheet, "Example");
  169. }
  170.  
  171.  
  172. /*    Send NNTP Command line to remote host & Check Response
  173. **    ------------------------------------------------------
  174. **
  175. ** On entry,
  176. **    command    points to the command to be sent, including CRLF, or is null
  177. **        pointer if no command to be sent.
  178. ** On exit,
  179. **    Negative status indicates transmission error, socket closed.
  180. **    Positive status is an NNTP status.
  181. */
  182.  
  183.  
  184. static int response(const char * command)
  185. {
  186.     int result;    
  187.     char * p = response_text;
  188.     if (command) {
  189.         int status = write(s, command, strlen(command));
  190.     if (status<0){
  191.         if (TRACE) printf(
  192.             "NewsAccess: Unable to send comand. Disconnecting.\n");
  193.         close(s);
  194.         s = -1;
  195.         return status;
  196.     } /* if bad status */
  197.     if (TRACE) printf("NNTP command sent: %s", command);
  198.     } /* if command to be sent */
  199.     
  200.     for(;;) {  
  201.     if (((*p++=NEXT_CHAR) == '\n') || (p == &response_text[LINE_LENGTH])) {
  202.         *p++=0;                /* Terminate the string */
  203.         if (TRACE) printf("NNTP Response: %s\n", response_text);
  204.         sscanf(response_text, "%i", &result);
  205.         return result;        
  206.     } /* if end of line */
  207.     
  208.     if (*(p-1) < 0) return -1;    /* End of file on response */
  209.     
  210.     } /* Loop over characters */
  211. }
  212.  
  213.  
  214. //    Case insensitive string comparisons
  215. //    -----------------------------------
  216. //
  217. // On entry,
  218. //    template must be already un upper case.
  219. //    unknown may be in upper or lower or mixed case to match.
  220. //
  221. static BOOL match(const char * unknown, const char * template)
  222. {
  223.     const char * u = unknown;
  224.     const char * t = template;
  225.     for (;*u && *t && (toupper(*u)==*t); u++, t++) /* Find mismatch or end */ ;
  226.     return (BOOL)(*t==0);        /* OK if end of template */
  227. }
  228.  
  229. //    Find Author's name in mail address
  230. //    ----------------------------------
  231. //
  232. // On exit,
  233. //    THE EMAIL ADDRESS IS CORRUPTED
  234. //
  235. // For example, returns "Tim Berners-Lee" if given any of
  236. //    " Tim Berners-Lee <tim@online.cern.ch> "
  237. //  or    " tim@online.cern.ch ( Tim Berners-Lee ) "
  238. //
  239. static char * author_name(char * email)
  240. {
  241.     char *s, *e;
  242.     
  243.     if ((s=index(email,'(')) && (e=index(email, ')')))
  244.         if (e>s) {
  245.         *e=0;            /* Chop off everything after the ')'  */
  246.         return HTStrip(s+1);    /* Remove leading and trailing spaces */
  247.     }
  248.     
  249.     if ((s=index(email,'<')) && (e=index(email, '>')))
  250.         if (e>s) {
  251.         strcpy(s, e+1);        /* Remove <...> */
  252.         return HTStrip(email);    /* Remove leading and trailing spaces */
  253.     }
  254.     
  255.     return HTStrip(email);        /* Default to the whole thing */
  256.     
  257.  
  258. }
  259.  
  260.  
  261. /*    Paste in an Anchor
  262. **    ------------------
  263. **
  264. **
  265. ** On entry,
  266. **    HT     has a selection of zero length at the end.
  267. **    text     points to the text to be put into the file, 0 terminated.
  268. **    addr    points to the hypertext refernce address,
  269. **        terminated by white space, comma, NULL or '>' 
  270. */
  271. static void write_anchor(const char * text, const char * addr)
  272. {
  273.     char href[LINE_LENGTH+1];
  274.         
  275.     {
  276.         const char * p;
  277.     strcpy(href,"news:");
  278.     for(p=addr; *p && (*p!='>') && !WHITE(*p) && (*p!=','); p++);
  279.         strncat(href, addr, p-addr);        /* Make complete hypertext reference */
  280.     }
  281.     
  282.     [HT appendBeginAnchor:"" to:href];
  283.     [HT appendText:text];
  284.     [HT appendEndAnchor];
  285. }
  286.  
  287. /*    Write list of anchors
  288. **    ---------------------
  289. **
  290. **    We take a pointer to a list of objects, and write out each,
  291. **    generating an anchor for each.
  292. **
  293. ** On entry,
  294. **    HT     has a selection of zero length at the end.
  295. **    text     points to a comma or space separated list of addresses.
  296. ** On exit,
  297. **    *text    is NOT any more chopped up into substrings.
  298. */
  299. static void write_anchors(char * text)
  300. {
  301.     char * start = text;
  302.     char * end;
  303.     char c;
  304.     for (;;) {
  305.         for(;*start && (WHITE(*start)); start++);  /* Find start */
  306.     if (!*start) return;                        /* (Done) */
  307.         for(end=start; *end && (*end!=' ') && (*end!=','); end++);    /* Find end */
  308.     if (*end) end++;        /* Include comma or space but not NULL */
  309.     c = *end;
  310.     *end = 0;
  311.     write_anchor(start, start);
  312.     *end = c;
  313.     start = end;            /* Point to next one */
  314.     }
  315. }
  316.  
  317. /*    Read in an Article
  318. **    ------------------
  319. */
  320. //
  321. //    Note the termination condition of a single dot on a line by itself.
  322. //    RFC 977 specifies that the line "folding" of RFC850 is not used, so we
  323. //    do not handle it here.
  324.         
  325. static void read_article()
  326. {
  327.  
  328.     char line[LINE_LENGTH+1];
  329.     char *references=NULL;            /* Hrefs for other articles */
  330.     char *newsgroups=NULL;            /* Newsgroups list */
  331.     char *p = line;
  332.     BOOL done = NO;
  333.     
  334. /*    Read in the HEADer of the article:
  335. **
  336. **    The header fields are either ignored, or formatted and put into the
  337. **     Text.
  338. */
  339.     if (diagnostic!=2)
  340. #ifdef APPEND
  341.     [HT appendStyle:addressStyle];
  342. #else
  343.     [HT applyStyle:addressStyle];
  344. #endif
  345.     while(!done){
  346.     if (((*p++=NEXT_CHAR) == '\n') || (p == &line[LINE_LENGTH])) {
  347.         *--p=0;                /* Terminate the string */
  348.         if (TRACE) printf("H %s\n", line);
  349.         if (line[0]=='.') {    
  350.         if (line[1]<' ') {        /* End of article? */
  351.             done = YES;
  352.             break;
  353.         }
  354.         
  355.         } else if (line[0]<' ') {
  356.         break;        /* End of Header? */
  357.         } else if (match(line, "SUBJECT:")) {
  358.         [HT setTitle:line+8];
  359.         } else if (match(line, "DATE:")
  360.            || match(line, "FROM:")
  361.            || match(line, "ORGANIZATION:")) {
  362.         strcat(line, "\n");
  363. #ifdef APPEND
  364.         [HT appendText:index(line,':')+1];
  365. #else
  366.         [HT replaceSel:index(line,':')+1 style:addressStyle];
  367. #endif        
  368.         } else if (match(line, "NEWSGROUPS:")) {
  369.         StrAllocCopy(newsgroups, HTStrip(index(line,':')+1));
  370.         
  371.         } else if (match(line, "REFERENCES:")) {
  372.         StrAllocCopy(references, HTStrip(index(line,':')+1));
  373.         
  374.         } /* end if match */
  375.         p = line;            /* Restart at beginning */
  376.     } /* if end of line */
  377.     } /* Loop over characters */
  378.  
  379. #ifdef APPEND
  380.     [HT appendText:"\n"];
  381.     [HT appendStyle:textStyle];
  382. #else
  383.     [HT replaceSel:"\n" style:addressStyle];
  384. #endif    
  385.  
  386.     if (newsgroups) {
  387.     [HT appendText: "\nNewsgroups: "];
  388.     write_anchors(newsgroups);
  389.     free(newsgroups);
  390.     }
  391.     
  392.     if (references) {
  393.     [HT appendText: "\nReferences: "];
  394.     write_anchors(references);
  395.     free(references);
  396.     }
  397.  
  398.     [HT appendText: "\n\n\n"];
  399.  
  400. //    Read in the BODY of the Article:
  401. //
  402.     p = line;
  403.     while(!done){
  404.     if (((*p++=NEXT_CHAR) == '\n') || (p == &line[LINE_LENGTH])) {
  405.         *p++=0;                /* Terminate the string */
  406.         if (TRACE) printf("B %s", line);
  407.         if (line[0]=='.') {
  408.         if (line[1]<' ') {        /* End of article? */
  409.             done = YES;
  410.             break;
  411.         } else {            /* Line starts with dot */
  412.             [HT appendText: &line[1]];    /* Ignore first dot */
  413.         }
  414.         } else {
  415.  
  416. /*    Normal lines are scanned for buried references to other articles.
  417. **    Unfortunately, it will pick up mail addresses as well!
  418. */
  419.         char *l = line;
  420.         char * p;
  421.         while (p=index(l, '<')) {
  422.             char *q=index(l,'>');
  423.             if (q>p && index(p,'@')) {
  424.                 char c = q[1];
  425.             q[1] = 0;        /* chop up */
  426.             *p = 0;
  427.             [HT appendText:l];
  428.             *p = '<';         /* again */
  429.             *q = 0;
  430.             [HT appendBeginAnchor:"" to:p+1];
  431.             *q = '>';         /* again */
  432.             [HT appendText:p];
  433.             [HT appendEndAnchor];
  434.             q[1] = c;        /* again */
  435.             l=q+1;
  436.             } else break;        /* line has unmatched <> */
  437.         } 
  438.         [HT appendText: l];        /* Last bit of the line */
  439.         } /* if not dot */
  440.         p = line;                /* Restart at beginning */
  441.     } /* if end of line */
  442.     } /* Loop over characters */
  443. }
  444.  
  445.  
  446. /*    Read in a List of Newsgroups
  447. **    ----------------------------
  448. */
  449. //
  450. //    Note the termination condition of a single dot on a line by itself.
  451. //    RFC 977 specifies that the line "folding" of RFC850 is not used, so we
  452. //    do not handle it here.
  453.         
  454. static void read_list()
  455. {
  456.  
  457.     char line[LINE_LENGTH+1];
  458.     char *p;
  459.     BOOL done = NO;
  460.     
  461. /*    Read in the HEADer of the article:
  462. **
  463. **    The header fields are either ignored, or formatted and put into the
  464. **    Text.
  465. */
  466. #ifdef APPEND
  467.     [HT appendText: "\nNewsgroups:\n\n"];    /* Should be haeding style */
  468. #else
  469.     [HT replaceSel:"\nNewsgroups:\n\n" style:textStyle];    /* Should be heading */
  470. #endif  
  471.     p = line;
  472.     while(!done){
  473.     if (((*p++=NEXT_CHAR) == '\n') || (p == &line[LINE_LENGTH])) {
  474.         *p++=0;                /* Terminate the string */
  475.         if (TRACE) printf("B %s", line);
  476.         if (line[0]=='.') {
  477.         if (line[1]<' ') {        /* End of article? */
  478.             done = YES;
  479.             break;
  480.         } else {            /* Line starts with dot */
  481. #ifdef APPEND
  482.             [HT appendText: &line[1]];
  483. #else
  484.             [HT replaceSel:&line[1] style:textStyle];    /* Ignore first dot */
  485. #endif
  486.         }
  487.         } else {
  488.  
  489. /*    Normal lines are scanned for references to newsgroups.
  490. */
  491.         char group[LINE_LENGTH];
  492.         int first, last;
  493.         char postable;
  494.         if (sscanf(line, "%s %i %i %c", group, &first, &last, &postable)==4)
  495.             write_anchor(line, group);
  496.         else
  497. #ifdef APPEND
  498.             [HT appendText:line];
  499. #else
  500.             [HT replaceSel:line style:textStyle];
  501. #endif
  502.         } /* if not dot */
  503.         p = line;            /* Restart at beginning */
  504.     } /* if end of line */
  505.     } /* Loop over characters */
  506. }
  507.  
  508.  
  509. /*    Read in a Newsgroup
  510. **    -------------------
  511. **    Unfortunately, we have to ask for each article one by one if we want more
  512. **    than one field.
  513. **
  514. */
  515. void read_group(const char * groupName, int first_required, int last_required)
  516. {
  517.     char line[LINE_LENGTH+1];
  518.     char author[LINE_LENGTH+1];
  519.     char subject[LINE_LENGTH+1];
  520.     char *p;
  521.     BOOL done;
  522.  
  523.     char buffer[LINE_LENGTH];
  524.     char *reference=0;            /* Href for article */
  525.     int art;                /* Article number WITHIN GROUP */
  526.     int status, count, first, last;    /* Response fields */
  527.                     /* count is only an upper limit */
  528.  
  529.     sscanf(response_text, " %i %i %i %i", &status, &count, &first, &last);
  530.     if(TRACE) printf("Newsgroup status=%i, count=%i, (%i-%i) required:(%i-%i)\n",
  531.                 status, count, first, last, first_required, last_required);
  532.     if (last==0) {
  533.         [HT appendText: "\nNo articles in this group.\n"];
  534.     return;
  535.     }
  536.     
  537. #define FAST_THRESHOLD 100    /* Above this, read IDs fast */
  538. #define CHOP_THRESHOLD 50    /* Above this, chop off the rest */
  539.  
  540.     if (first_required<first) first_required = first;        /* clip */
  541.     if ((last_required==0) || (last_required > last)) last_required = last;
  542.     
  543.     if (last_required<=first_required) {
  544.         [HT appendText: "\nNo articles in this range.\n"];
  545.     return;
  546.     }
  547.  
  548.     if (last_required-first_required+1 > MAX_CHUNK) {    /* Trim this block */
  549.         first_required = last_required-CHUNK_SIZE+1;
  550.     }
  551.     if (TRACE) printf ("    Chunk will be (%i-%i)\n", first_required, last_required);
  552.  
  553. /*    Link to earlier articles
  554. */
  555.     if (first_required>first) {
  556.         int before;            /* Start of one before */
  557.     if (first_required-MAX_CHUNK <= first) before = first;
  558.     else before = first_required-CHUNK_SIZE;
  559.         sprintf(buffer, "%s/%i-%i", groupName, before, first_required-1);
  560.     if (TRACE) printf("    Block before is %s\n", buffer);
  561.     [HT appendBeginAnchor:"" to:buffer];
  562.     [HT appendText: " (Earlier articles...)\n\n"];
  563.     [HT appendEndAnchor];
  564.     }
  565.     
  566.     done = NO;
  567.  
  568. /*#define USE_XHDR*/
  569. #ifdef USE_XHDR
  570.     if (count>FAST_THRESHOLD)  {
  571.         sprintf(buffer,
  572.     "\nThere are about %i articles currently available in %s, IDs as follows:\n\n",
  573.         count, groupName); 
  574.         [HT appendText:buffer];
  575.         anchor_start = [HT textLength];
  576.         sprintf(buffer, "XHDR Message-ID %i-%i\n", first, last);
  577.     status = response(buffer);
  578.     if (status==221) {
  579.  
  580.         p = line;
  581.         while(!done){
  582.         if (((*p++=NEXT_CHAR) == '\n') || (p == &line[LINE_LENGTH])) {
  583.             *p++=0;                /* Terminate the string */
  584.             if (TRACE) printf("X %s", line);
  585.             if (line[0]=='.') {
  586.             if (line[1]<' ') {        /* End of article? */
  587.                 done = YES;
  588.                 break;
  589.             } else {            /* Line starts with dot */
  590.                     /* Ignore strange line */
  591.             }
  592.             } else {
  593.     
  594.     /*    Normal lines are scanned for references to articles.
  595.     */
  596.             char * space = strchr(line, ' ');
  597.             if (space++)
  598.                 write_anchor(space, space);
  599.             } /* if not dot */
  600.             p = line;            /* Restart at beginning */
  601.         } /* if end of line */
  602.         } /* Loop over characters */
  603.  
  604.         /* leaving loop with "done" set */
  605.     } /* Good status */
  606.     };
  607. #endif
  608.  
  609. /*    Read newsgroup using individual fields:
  610. */
  611.     if (!done) {
  612.         if (first==first_required && last==last_required)
  613.         [HT appendText:"\nAll available articles:\n\n"];
  614.         else [HT appendText: "\nArticles:\n\n"];
  615.     for(art=first_required; art<=last_required; art++) {
  616.     
  617. /*#define OVERLAP*/
  618. #ifdef OVERLAP
  619. /*    With this code we try to keep the server running flat out by queuing just
  620. **    one extra command ahead of time. We assume (1) that the server won't abort if
  621. **    it get input during output, and (2) that TCP buffering is enough for the
  622. **    two commands. Both these assumptions seem very reasonable. However, we HAVE had
  623. **    a hangup with a loaded server.
  624. */
  625.         if (art==first_required) {
  626.         if (art==last_required) {
  627.             sprintf(buffer, "HEAD %i\n", art);    /* Only one */
  628.             status = response(buffer);
  629.             } else {                    /* First of many */
  630.             sprintf(buffer, "HEAD %i\nHEAD %i\n", art, art+1);
  631.             status = response(buffer);
  632.             }
  633.         } else if (art==last_required) {            /* Last of many */
  634.             status = response(NULL);
  635.         } else {                        /* Middle of many */
  636.             sprintf(buffer, "HEAD %i\n", art+1);
  637.             status = response(buffer);
  638.         }
  639. #else
  640.         sprintf(buffer, "HEAD %i\n", art);
  641.         status = response(buffer);
  642. #endif
  643.         if (status == 221) {    /* Head follows - parse it:*/
  644.     
  645.         p = line;                /* Write pointer */
  646.         done = NO;
  647.         while(!done){
  648.             if (   ((*p++=NEXT_CHAR) == '\n')
  649.             || (p == &line[LINE_LENGTH]) ) {
  650.             
  651.             *--p=0;            /* Terminate  & chop LF*/
  652.             p = line;            /* Restart at beginning */
  653.             if (TRACE) printf("G %s\n", line);
  654.             switch(line[0]) {
  655.     
  656.             case '.':
  657.                 done = (line[1]<' ');        /* End of article? */
  658.                 break;
  659.     
  660.             case 'S':
  661.             case 's':
  662.                 if (match(line, "SUBJECT:"))
  663.                 strcpy(subject, line+8);    /* Save author */
  664.                 break;
  665.     
  666.             case 'M':
  667.             case 'm':
  668.                 if (match(line, "MESSAGE-ID:")) {
  669.                 char * addr = HTStrip(line+11) +1;    /* Chop < */
  670.                 addr[strlen(addr)-1]=0;        /* Chop > */
  671.                 StrAllocCopy(reference, addr);
  672.                 }
  673.                 break;
  674.     
  675.             case 'f':
  676.             case 'F':
  677.                 if (match(line, "FROM:"))
  678.                 strcpy(author, author_name(index(line,':')+1));
  679.                 break;
  680.                     
  681.             } /* end switch on first character */
  682.             } /* if end of line */
  683.         } /* Loop over characters */
  684.     
  685.         sprintf(buffer, "\"%s\" - %s\n", subject, author);
  686.         if (reference) {
  687.             write_anchor(buffer, reference);
  688.             free(reference);
  689.             reference=0;
  690.         } else {
  691.             [HT appendText:buffer];
  692.         }
  693.         
  694.     
  695. /*    Change the title bar to indicate progress!
  696. */
  697.         if (art%10 == 0) {
  698.             sprintf(buffer, "Reading newsgroup %s,  Article %i (of %i-%i) ...",
  699.                 groupName, art, first, last);
  700.             [HT setTitle:buffer];
  701.         }
  702.     
  703.         } /* If good response */
  704.     } /* Loop over article */        
  705.     } /* If read headers */
  706.     
  707. /*    Link to later articles
  708. */
  709.     if (last_required<last) {
  710.         int after;            /* End of article after */
  711.     after = last_required+CHUNK_SIZE;
  712.         if (after==last) sprintf(buffer, "news:%s", groupName);    /* original group */
  713.         else sprintf(buffer, "news:%s/%i-%i", groupName, last_required+1, after);
  714.     if (TRACE) printf("    Block after is %s\n", buffer);
  715.     [HT appendBeginAnchor:"" to:buffer];
  716.     [HT appendText: "\n(Later articles...)\n"];
  717.     [HT appendEndAnchor];
  718.     }
  719.     
  720. /*    Set window title
  721. */
  722.     sprintf(buffer, "Newsgroup %s,  Articles %i-%i",
  723.             groupName, first_required, last_required);
  724.     [HT setTitle:buffer];
  725.  
  726. }
  727.  
  728.  
  729. //    Open by name                    -accessName:anchor:diagnostic:
  730. //    ------------
  731.     
  732. - accessName:(const char *)arg
  733.     anchor:(Anchor *)anAnchor
  734.     diagnostic:(int)diag
  735. {
  736.     char command[257];            /* The whole command */
  737.     char groupName[GROUP_NAME_LENGTH];    /* Just the group name */
  738.     int status;                /* tcp return */
  739.     int retries;            /* A count of how hard we have tried */ 
  740.     BOOL group_wanted;            /* Flag: group was asked for, not article */
  741.     BOOL list_wanted;            /* Flag: group was asked for, not article */
  742.     int first, last;            /* First and last articles asked for */
  743.  
  744.     diagnostic = diag;            /* set global flag */
  745.     
  746.     if (TRACE) printf("NewsAccess: Looking for %s\n", arg);
  747.     get_styles();
  748.     {
  749.         char * p1;
  750.  
  751. /*    We will ask for the document, omitting the host name & anchor.
  752. **
  753. **    Syntax of address is
  754. **        xxx@yyy            Article
  755. **        <xxx@yyy>        Same article
  756. **        xxxxx            News group (no "@")
  757. */        
  758.     group_wanted = (index(arg, '@')==0) && (index(arg, '*')==0);
  759.     list_wanted  = (index(arg, '@')==0) && (index(arg, '*')!=0);
  760.     
  761.     p1 = HTParse(arg, "", PARSE_PATH|PARSE_PUNCTUATION);
  762.     if (list_wanted) {
  763.         strcpy(command, "LIST ");
  764.     } else if (group_wanted) {
  765.         char * slash = strchr(p1, '/');
  766.         strcpy(command, "GROUP ");
  767.         first = 0;
  768.         last = 0;
  769.         if (slash) {
  770.         *slash = 0;
  771.         strcpy(groupName, p1);
  772.         *slash = '/';
  773.         (void) sscanf(slash+1, "%i-%i", &first, &last);
  774.         } else {
  775.         strcpy(groupName, p1);
  776.         }
  777.         strcat(command, groupName);
  778.     } else {
  779.         strcpy(command, "ARTICLE ");
  780.         if (index(p1, '<')==0) strcat(command,"<");
  781.         strcat(command, p1);
  782.         if (index(p1, '>')==0) strcat(command,">");
  783.     }
  784.     free(p1);
  785.  
  786.         strcat(command, "\r\n");        /* CR LF, as in rfc 977 */
  787.     
  788.     } /* scope of p1 */
  789.     
  790.     if (!*arg) return nil;            // Ignore if no name
  791.  
  792.     
  793. //    Make a hypertext object with an anchor list.
  794.         
  795.     HT = [HyperText newAnchor:anAnchor Server:self];
  796.  
  797.     [HT setupWindow];            
  798.     [HT selectText:self];        /* Replace everything with what's to come */
  799.     
  800. //    Now, let's get a stream setup up from the NewsHost:
  801.         
  802.     for(retries=0;retries<2; retries++){
  803.     
  804.         if (s<0) {
  805.             [[HT window]setTitle:"Connecting to NewsHost ..."];    /* Tell user  */
  806.         s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
  807.         status = connect(s, (struct sockaddr*)&soc_address, sizeof(soc_address));
  808.         if (status<0){
  809.         char message[256];
  810.             close(s);
  811.         s=-1;
  812.         if (TRACE) printf("NewsAccess: Unable to connect to news host.\n");
  813. /*        if (retries<=1) continue;   WHY TRY AGAIN ?     */
  814.         NXRunAlertPanel(NULL,
  815.                 "Could not access newshost %s.",
  816.             NULL,NULL,NULL,
  817.             NewsHost);
  818.         sprintf(message,
  819. "\nCould not access %s.\n\n (Check default WorldWideWeb NewsHost ?)\n",
  820.             NewsHost);
  821.         [HT setText:message];
  822.         return HT;
  823.         } else {
  824.         if (TRACE) printf("NewsAccess: Connected to news host %s.\n",
  825.                 NewsHost);
  826.         if ((response(NULL) / 100) !=2) {
  827.             close(s);
  828.             s=-1;
  829.             NXRunAlertPanel("News access",
  830.                 "Could not retrieve information:\n   %s.",
  831.                 NULL,NULL,NULL,
  832.                 response_text);
  833.                 [[HT window]setTitle: "News host response"];
  834.             [HT setText:response_text];
  835.             return HT;
  836.         }
  837.         }
  838.     } /* If needed opening */
  839.     
  840.         [[HT window]setTitle:arg];        /* Tell user something's happening */
  841.     status = response(command);
  842.     if (status<0) break;
  843.     if ((status/ 100) !=2) {
  844.         NXRunAlertPanel("News access", response_text,
  845.             NULL,NULL,NULL);
  846.         [HT setText:response_text];
  847.         close(s);
  848.         s=-1;
  849.         // return HT; -- no:the message might be "Timeout-disconnected" left over
  850.         continue;    //    Try again
  851.     }
  852.   
  853. //    Load a group, article, etc
  854. //
  855.         [HT appendBegin];
  856.     
  857.     if (list_wanted) read_list();
  858.     else if (group_wanted) read_group(groupName, first, last);
  859.         else read_article();
  860.  
  861.     [HT appendEnd];
  862.  
  863.         [HT setEditable:NO];        /* This is read-only data */
  864.     [HT adjustWindow];
  865.     return HT;
  866.     
  867.     } /* Retry loop */
  868.     
  869.     [HT setText:"Sorry, could not load requested news.\n"];
  870.     
  871. /*    NXRunAlertPanel(NULL, "Sorry, could not load `%s'.",
  872.             NULL,NULL,NULL, arg);    No -- message earlier wil have covered it */
  873.     return HT;
  874. }
  875.  
  876. //        Actions:
  877. //        =======
  878.  
  879.  
  880. //    This will load an anchor which has a name
  881. //    -----------------------------------------
  882. //
  883. // On entry,
  884. //    Anchor's address is valid.
  885. // On exit:
  886. //    If there is no success, nil is returned.
  887. //    Otherwise, the anchor is returned.
  888. //
  889.  
  890. - loadAnchor: (Anchor *) a Diagnostic:(int)diagnostic
  891. {
  892.     HyperText * HT;
  893.  
  894.     if (![a node]) {
  895.         HT = [self accessName:[a address] anchor:a diagnostic:diagnostic];
  896.         if (!HT) return nil;
  897.     [[HT window] setDocEdited:NO];
  898.     }
  899.     return a;
  900. }
  901. @end
  902.